package storage

import (
	"fmt"
	"reflect"
	"time"
	"webgo/server/plugin"
	"webgo/server/util"
)

var (
	Account AccountType

	store = map[string]any{
		"account": &Account,
	}
)

type Type[STATIC, DYNAMICS any] struct {
	config struct {
		collection string
	}
	List []*UnitType[STATIC, DYNAMICS]
}

type UnitType[STATIC, DYNAMICS any] struct {
	Id        plugin.MongoObjectId `form:"id" bson:"_id" json:"id"`
	AccountId plugin.MongoObjectId `form:"account_id" bson:"account_id" json:"account_id"`

	UpdateTime UpdateTimeType `form:"update_time" bson:"update_time" json:"update_time"`
	Disabled   bool           `form:"disabled" bson:"disabled" json:"disabled"`

	Static   STATIC   `form:"static" bson:"static" json:"static"`
	Dynamics DYNAMICS `form:"dynamics" bson:"dynamics" json:"dynamics"`
}

type UpdateTimeType struct {
	Base     int64 `form:"base" bson:"base" json:"base"`
	Dynamics int64 `form:"dynamics" bson:"dynamics" json:"dynamics"`
	Static   int64 `form:"static" bson:"static" json:"static"`
}

func Init() (warn, err error) {

	for collection, item := range store {

		var (
			_item = reflect.ValueOf(item)
		)

		_item.MethodByName("SetCollection").Call([]reflect.Value{reflect.ValueOf(collection)})

		if err = plugin.Mongo.FindMany(collection, _item.Elem().FieldByName("List").Addr().Interface()); err != nil {
			return
		}
	}

	return
}

func Exit() {
}

func SyncUpdate(account_id *plugin.MongoObjectId, _update_time *UpdateTimeType) (data map[string][]any) {

	data = map[string][]any{}

	for collection, item := range store {

		data[collection] = []any{}

		var (
			_item      = reflect.ValueOf(item)
			list_field = _item.Elem().FieldByName("List")
		)

		for idx := 0; idx < list_field.Len(); idx++ {

			var (
				unit        = list_field.Index(idx).Elem()
				update_time = unit.FieldByName("UpdateTime")

				uni_data = map[string]any{}
			)

			if account_id != nil && *account_id != unit.FieldByName("AccountId").Interface() {
				continue
			}

			if update_time.FieldByName("Base").Int() > _update_time.Base {
				data[collection] = append(data[collection], unit.Addr().Interface())
				continue
			}

			if update_time.FieldByName("Static").Int() > _update_time.Static {
				uni_data["static"] = unit.FieldByName("Static").Addr().Interface()
			}

			if update_time.FieldByName("Dynamics").Int() > _update_time.Dynamics {
				uni_data["dynamics"] = unit.FieldByName("Dynamics").Addr().Interface()
			}

			if len(uni_data) > 0 {

				uni_data["update_time"] = update_time.Addr().Interface()

				data[collection] = append(data[collection], uni_data)
			}
		}
	}

	return
}

func (_self *Type[STATIC, DYNAMICS]) SetCollection(collection string) {

	_self.config.collection = collection
}

func (_self *Type[STATIC, DYNAMICS]) foreach(account_id *plugin.MongoObjectId, callback func(stop *bool, index *int, item *UnitType[STATIC, DYNAMICS])) {

	var (
		stop bool
	)

	for index, item := range _self.List {

		if item == nil {
			continue
		}

		if account_id != nil && item.AccountId == *account_id {
			continue
		}

		if callback(&stop, &index, item); stop {
			return
		}
	}
}

func (_self *Type[STATIC, DYNAMICS]) Fetch(account_id *plugin.MongoObjectId) (list []*UnitType[STATIC, DYNAMICS]) {

	list = []*UnitType[STATIC, DYNAMICS]{}

	_self.foreach(account_id, func(stop *bool, index *int, item *UnitType[STATIC, DYNAMICS]) {

		list = append(list, item)
	})

	return
}

func (_self *Type[STATIC, DYNAMICS]) Existed(account_id *plugin.MongoObjectId, id plugin.MongoObjectId) (result *UnitType[STATIC, DYNAMICS]) {

	_self.foreach(account_id, func(stop *bool, index *int, item *UnitType[STATIC, DYNAMICS]) {

		if item.Id == id {
			result = item
			*stop = true
		}
	})

	return
}

func (_self *Type[STATIC, DYNAMICS]) SetDisabled(account_id *plugin.MongoObjectId, id plugin.MongoObjectId, value bool) (err error) {

	var (
		object = _self.Existed(account_id, id)
	)

	if object == nil {
		err = fmt.Errorf("not existed")
		return
	}

	if err = plugin.Mongo.Update(_self.config.collection, object.Id, map[string]any{
		"diabled": value,
	}); err != nil {
		return
	}

	object.Disabled = value

	return
}

func (_self *Type[STATIC, DYNAMICS]) Insert(account_id *plugin.MongoObjectId, table *UnitType[STATIC, DYNAMICS]) (err error) {

	table.Id = plugin.NewMongoObjectId()

	if account_id != nil {
		table.AccountId = *account_id
	}

	table.UpdateTime.Base = time.Now().UnixMilli()
	table.UpdateTime.Static = time.Now().UnixMilli()
	table.UpdateTime.Dynamics = time.Now().UnixMilli()

	if err = plugin.Mongo.Insert(_self.config.collection, table); err != nil {
		return
	}

	_self.List = append(_self.List, table)

	return
}

func (_self *Type[STATIC, DYNAMICS]) Delete(account_id *plugin.MongoObjectId, id plugin.MongoObjectId) (err error) {

	if err = plugin.Mongo.Delete(_self.config.collection, id); err != nil {
		return
	}

	_self.foreach(account_id, func(stop *bool, index *int, item *UnitType[STATIC, DYNAMICS]) {

		if item.Id == id {
			_self.List[*index] = nil
			*stop = true
		}
	})

	return
}

func (_self *Type[STATIC, DYNAMICS]) Update(account_id *plugin.MongoObjectId, id plugin.MongoObjectId, callback_static func(static *STATIC), callback_dynamics func(dynamics *DYNAMICS)) (err error) {

	defer util.Recover(&err)

	var (
		object = _self.Existed(account_id, id)
		now    = time.Now().UnixMilli()

		diffrents  = map[string]any{}
		_diffrents = map[string]any{}

		old any
		new any
		tag string
	)

	if object == nil {
		err = fmt.Errorf("not existed")
		return
	}

	switch {

	case callback_static != nil:

		var (
			static = object.Static
		)

		callback_static(&static)

		old = &object.Static
		new = &static
		tag = "static"

	case callback_dynamics != nil:

		var (
			dynamics = object.Dynamics
		)

		callback_dynamics(&dynamics)

		old = &object.Dynamics
		new = &dynamics
		tag = "dynamics"
	}

	if util.GetDeffient(old, new, "", diffrents); len(diffrents) == 0 {
		err = fmt.Errorf("not change")
		return
	}

	for key, value := range diffrents {
		_diffrents[tag+"."+key] = &value
	}

	_diffrents["update_time."+tag] = now

	if err = plugin.Mongo.Update(_self.config.collection, object.Id, _diffrents); err != nil {
		return
	}

	util.SetDifferent(old, diffrents)

	switch {

	case callback_static != nil:

		object.UpdateTime.Static = now

	case callback_dynamics != nil:

		object.UpdateTime.Dynamics = now
	}

	return
}
